package davidlauzon.activerecord;


import java.sql.SQLException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import davidlauzon.activerecord.connection.ConnectionAdapter;
import davidlauzon.activerecord.manager.DeleteManager;
import davidlauzon.activerecord.manager.InsertManager;
import davidlauzon.activerecord.manager.SelectManager;
import davidlauzon.activerecord.manager.UpdateManager;
import davidlauzon.activerecord.nodes.Table;
import davidlauzon.activerecord.nodes.field.Field;
import davidlauzon.util.ArrayUtil;


public abstract class ActiveRecord
{
    /*********************************************************************************************
     * CONSTANTS
     *********************************************************************************************/
    static public final String LOG_TAG = ActiveRecord.class.getSimpleName();
    
    
    /*********************************************************************************************
     * VARIABLES
     *********************************************************************************************/
    private boolean _isNewRecord = true;
    private boolean _wasSaved;
    private Map<Field, Object> _attributes;
    
    
    /*********************************************************************************************
     * SIMPLE GETTERS & SETTERS
     *********************************************************************************************/
    public boolean isNewRecord() { return _isNewRecord; }
    public boolean wasSaved()    { return _wasSaved; }

    public Object get(Field field) { return _attributes.get(field); }
    public void   set(Field field, Object value) {  _attributes.put(field, value); }
    public Map<Field,Object> getAttributes()  { return _attributes; }
    
    
    
    /*********************************************************************************************
     * ABSTRACT METHODS
     *********************************************************************************************/
    public abstract Table        getTable();
    public abstract Field[]      getFields();
    public abstract ActiveRecord getStaticInstance();
    
    
    /*********************************************************************************************
     * STATIC METHODS
     *********************************************************************************************/
    static public <T extends ActiveRecord> T build( Class<ActiveRecord> klass, Map<Field, Object> attributes )
    {
        T record = null;
        try {
            record = (T) klass.newInstance(); // TODO: FIXME: SHOULD NOT need this
            
            // We want to clone the attributes
            if (attributes != null)
                for ( Field field : attributes.keySet() )
                    record.set( field, attributes.get(field) );
            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        return record;
    }
    
    static public <T extends ActiveRecord> T create(Class<ActiveRecord> klass, Map<Field, Object> attributes) throws SQLException
    {
        T record = build( klass, attributes );
        
        record.doInsert();
        
        return record;
    }
    
    static public boolean update(Class<ActiveRecord> klass, long id, Map<Field, Object> attributes) throws SQLException
    {
        ActiveRecord record = build( klass, attributes );

        record.set( record.getPrimaryKey(), id);
        
        return record.doUpdate();
    }
    
    static public boolean delete(Class<ActiveRecord> klass, long id) throws SQLException
    {
        ActiveRecord record = build( klass, null );
        
        record.set( record.getPrimaryKey(), id);
        
        return record.doDelete();
    }
    
    static public boolean delete(Class<ActiveRecord> klass, List<Long> ids) throws SQLException
    {
        ActiveRecord  record    = build( klass, null );
        DeleteManager deleteMan = record.buildDeleteManager();
        
        String idlist = ArrayUtil.join(ids.toArray(), ',');
        
        return deleteMan.where("id IN (" + idlist + ")").execute() > 0;
    }
    
    static public SelectManager find(Class<ActiveRecord> klass, String condition) throws SQLException
    {
        // FIXME Hack : should not need to instanciate : need better workaround!
        ActiveRecord record = build( klass, null );
        
        return record.doFind(klass).where(condition);
    }
    
    static public SelectManager find(Class<ActiveRecord> klass) throws SQLException
    {
        // FIXME Hack : should not need to instanciate : need better workaround!
        ActiveRecord record = build( klass, null );
        
        return record.doFind(klass);
    }
    
    
    
    
    /*********************************************************************************************
     * PUBLIC INSTANCE METHODS
     *********************************************************************************************/
    public ActiveRecord() {
        _isNewRecord = true;
        _wasSaved    = false;
        _attributes  = new HashMap<Field, Object>();
    }
    
    public boolean save() throws SQLException {
        return _isNewRecord ? doInsert() : doUpdate();
    }
    
    public boolean delete() throws SQLException {
        return doDelete();
    }
    

    public Float getFloat(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Float) {
            return (Float) source;
        } else if (source instanceof Double) {
            return ((Double) source).floatValue();
        } else {
            return null;
        }
    }
    
    public Double getDouble(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Double) {
            return (Double) source;
        } else if (source instanceof Float) {
            return ((Float) source).doubleValue();
        } else {
            return null;
        }
    }
    
    public Short getShort(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Short) {
            return (Short) source;
        } else if (source instanceof Long) {
            return ((Long) source).shortValue();
        } else if (source instanceof Integer) {
            return ((Integer) source).shortValue();
        } else {
            return null;
        }
    }
    
    public Integer getInt(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Short) {
            return ((Short) source).intValue();
        } else if (source instanceof Long) {
            return ((Long) source).intValue();
        } else if (source instanceof Integer) {
            return (Integer) source;
        } else {
            return null;
        }
    }
    
    public Long getLong(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Short) {
            return ((Short) source).longValue();
        } else if (source instanceof Long) {
            return (Long) source;
        } else if (source instanceof Integer) {
            return ((Integer) source).longValue();
        } else {
            return null;
        }
    }
    
    public Date getDate(Field field)
    {
        Object source = get(field);
        
        if (source instanceof Date) {
            return (Date) source;
        } else if (source instanceof String) {
            return new Date(Date.parse((String) source));
        } else {
            return null;
        }
    }
    
    public void insertLater() throws SQLException
    {
        buildInsertManager().values(getAttributes()).insertLater();
    } 

    
    
    
    /*********************************************************************************************
     * INTERNAL METHODS
     *********************************************************************************************/
    protected boolean doInsert() throws SQLException {
       InsertManager ins = buildInsertManager();
       ins.values(_attributes);
       long id = ins.insert();
       
//        long id = buildInsertManager().values( _attributes ).insert();
        if (id > 0)
            set( getPrimaryKey(), id);
            
        _isNewRecord = !(id > 0);
        _wasSaved    = (id > 0);
        
        return _wasSaved;
    }
    
    protected boolean doUpdate() throws SQLException {
        long id = getPrimaryKeyValue();
        long nbRows = buildUpdateManager().set( _attributes ).
                    where(getPrimaryKey() + " = ?")._(id).execute();
        
        _wasSaved    = (nbRows > 0);
        
        return _wasSaved;
    }
    
    protected boolean doDelete() throws SQLException {
        long id     = getPrimaryKeyValue();
        long nbRows = buildDeleteManager().where(getPrimaryKey() + " = ?")._(id).execute();
        
        _wasSaved    = (nbRows > 0);
        
        return _wasSaved;
    }
    
    protected SelectManager doFind(Class klass) throws SQLException {
        return buildSelectManager(klass);
    }
    
    protected ConnectionAdapter getConnection() {
        return getTable().getConnection();
    }
    
    public InsertManager buildInsertManager() {
        return (new InsertManager()).into( getTable() );
    }
    
    private UpdateManager buildUpdateManager() {
        return (new UpdateManager()).table(getTable());
    }
    
    private DeleteManager buildDeleteManager() {
        return (new DeleteManager()).from(getTable());
    }
    
    private SelectManager buildSelectManager(Class klass) {
        return (new SelectManager(klass)).from(getTable());
    }
    
    protected Field getPrimaryKey() {
        for (Field field : getFields()) {
            if (field.isPrimaryKey())
                return field;
        }
        return null;
    }
    
    protected long getPrimaryKeyValue() {
        return (Long) get( getPrimaryKey() );
    }
}
